using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace MKDS_Course_Editor.GCN.BMD
{
    class VTX1
    {
        public class Vtx1Header
        {
            public string tag; //'VTX1'
            public UInt32 sizeOfSection;
            public UInt32 arrayFormatOffset; //for each offsets[i] != 0, an ArrayFormat
            //is stored for that offset
            //offset relative to Vtx1Header start

            /*
              content is described by ArrayFormat - for each offset != 0,
              an ArrayFormat struct is stored at Vtx1Header.arrayFormatOffset
            */
            public UInt32[] offsets = new UInt32[13]; //offsets relative to Vtx1Header start
        }

        public class ArrayFormat
        {
            //see ogc/gx.h for a more complete list of these values:
            public UInt32 arrayType; //9: coords, a: normal, b: color, d: tex0 (gx.h: "Attribute")
            public UInt32 componentCount; //meaning depends on dataType (gx.h: "CompCount")
            public UInt32 dataType; //3: s16, 4: float, 5: rgba8 (gx.h: "CompType")

            //values i've seem for this: 7, e, 8, b, 0
            //-> number of mantissa bits for fixed point numbers!
            //(position of decimal point)
            public byte decimalPoint;
            public byte unknown3; //seems to be always 0xff
            public UInt16 unknown4; //seems to be always 0xffff
        }

        public class Color
        {
            public byte r, g, b, a;

            public void setRGBA(float ri, float gi, float bi, float ai)
            {
                r = (byte)(ri + .5f);
                g = (byte)(gi + .5f);
                b = (byte)(bi + .5f);
                a = (byte)(ai + .5f);
            }
        }

        public class TexCoord
        {
            public float s, t;

            public void setST(float si, float ti)
            {
                s = si;
                t = ti;
            }
        }

        public class Vtx1
        {
            public List<OpenTK.Vector3> positions = new List<OpenTK.Vector3>();
            public List<OpenTK.Vector3> normals = new List<OpenTK.Vector3>();
            public List<Color>[] colors = new List<Color>[2];
            public List<TexCoord>[] texCoords = new List<TexCoord>[8];
        }



        private static int getLength(Vtx1Header h, int k)
        {
            int startOffset = (int)h.offsets[k];
            for (int i = k + 1; i < 13; ++i)
            {
                if (h.offsets[i] != 0)
                    return (int)h.offsets[i] - startOffset;
            }
            return (int)h.sizeOfSection - startOffset;
        }

        private static void readVertexArray(Vtx1 arrays, ArrayFormat af, int length,
                             EndianBinaryReader er, long offset)
        {
            er.BaseStream.Position = offset;

            //convert array to float (so we have n + m cases, not n*m)
            List<float> data = new List<float>();
            switch (af.dataType)
            {
                case 3: //s16 fixed point
                    {
                        List<short> tmp = new List<short>();
                        tmp.AddRange(er.ReadInt16s(length / 2));
                        data.AddRange(new float[length / 2]);
                        float scale = (float)Math.Pow(0.5d, (double)af.decimalPoint);
                        for (int j = 0; j < data.Count; ++j)
                        {
                            Helper.toSHORT((ushort)tmp[j]);
                            data[j] = tmp[j] * scale;
                        }
                    } break;

                case 4: //f32
                    {
                        for (int i = 0; i < length / 4; i++)
                        {
                            data.Add(er.ReadInt32());
                            Helper.toFLOAT(data[i]);
                        }
                    } break;

                case 5: //rgb(a)
                    {
                        List<byte> tmp = new List<byte>();
                        tmp.AddRange(er.ReadBytes(length));
                        for (int j = 0; j < data.Count; ++j)
                            data.Add(tmp[j]);
                    } break;

                default:
                    //warn("vtx1: unknown array data type %d", af.dataType);
                    return;
            }

            //stuff floats into appropriate vertex array
            switch (af.arrayType)
            {
                case 9: //positions
                    {
                        if (af.componentCount == 0) //xy
                        {
                            arrays.positions.AddRange(new OpenTK.Vector3[data.Count / 2]);
                            for (int j = 0, k = 0; j < arrays.positions.Count; ++j, k += 2)
                            {
                                arrays.positions[j] = new OpenTK.Vector3(data[k], data[k + 1], 0);
                            }
                        }
                        else if (af.componentCount == 1) //xyz
                        {
                            arrays.positions.AddRange(new OpenTK.Vector3[data.Count / 3]);
                            for (int j = 0, k = 0; j < arrays.positions.Count; ++j, k += 3)
                                arrays.positions[j] = new OpenTK.Vector3(data[k], data[k + 1], data[k + 2]);
                        }
                        else
                        {
                            //warn("vtx1: unsupported componentCount for positions array: %d",
                            //af.componentCount);
                        }
                    } break;

                case 0xa: //normals
                    {
                        if (af.componentCount == 0) //xyz
                        {
                            arrays.normals.AddRange(new OpenTK.Vector3[data.Count / 3]);
                            for (int j = 0, k = 0; j < arrays.normals.Count; ++j, k += 3)
                                arrays.normals[j] = new OpenTK.Vector3(data[k], data[k + 1], data[k + 2]);
                        }
                        else
                        {
                            //warn("vtx1: unsupported componentCount for normals array: %d",
                            //  af.componentCount);
                        }
                    } break;

                case 0xb: //color0
                case 0xc: //color1
                    {
                        int index = (int)af.arrayType - 0xb;
                        if (af.componentCount == 0) //rgb
                        {
                            arrays.colors[index].AddRange(new Color[data.Count / 3]);
                            for (int j = 0, k = 0; j < arrays.colors[index].Count; ++j, k += 3)
                                arrays.colors[index][j].setRGBA(data[k], data[k + 1], data[k + 2],
                                  255.0f);
                        }
                        else if (af.componentCount == 1) //rgba
                        {
                            arrays.colors[index].AddRange(new Color[data.Count / 4]);
                            for (int j = 0, k = 0; j < arrays.colors[index].Count; ++j, k += 4)
                                arrays.colors[index][j].setRGBA(data[k], data[k + 1], data[k + 2],
                                  data[k + 3]);
                        }
                        else
                        {
                            //warn("vtx1: unsupported componentCount for colors array %d: %d",
                            //  index, af.componentCount);
                        }

                    } break;

                //texcoords 0 - 7
                case 0xd:
                case 0xe:
                case 0xf:
                case 0x10:
                case 0x11:
                case 0x12:
                case 0x13:
                case 0x14:
                    {
                        int index = (int)af.arrayType - 0xd;
                        if (af.componentCount == 0) //s
                        {
                            arrays.colors[index].AddRange(new Color[data.Count]);
                            for (int j = 0; j < arrays.texCoords[index].Count; ++j)
                                arrays.texCoords[index][j].setST(data[j], 0);
                        }
                        else if (af.componentCount == 1) //st
                        {
                            arrays.colors[index].AddRange(new Color[data.Count / 2]);
                            for (int j = 0, k = 0; j < arrays.texCoords[index].Count; ++j, k += 2)
                                arrays.texCoords[index][j].setST(data[k], data[k + 1]);
                        }
                        else
                        {
                            //warn("vtx1: unsupported componentCount for texcoords array %d: %d",
                            //  index, af.componentCount);
                        }
                    } break;


                default:
                    //warn("vtx1: unknown array type %d", af.arrayType);
                    break;
            }
        }

        private static void readVtx1Header(EndianBinaryReader er, Vtx1Header h)
        {
            h.tag = er.ReadString(Encoding.ASCII, 4);
            h.sizeOfSection = er.ReadUInt32();
            h.arrayFormatOffset = er.ReadUInt32();
            for (int i = 0; i < 13; ++i)
                h.offsets[i] = er.ReadUInt32();
        }

        private static void readArrayFormat(EndianBinaryReader er, ArrayFormat af)
        {
            af.arrayType = er.ReadUInt32();
            af.componentCount = er.ReadUInt32();
            af.dataType = er.ReadUInt32();
            af.decimalPoint = er.ReadByte();
            af.unknown3 = er.ReadByte();
            af.unknown4 = er.ReadUInt16();
        }

        private static int countArrays(Vtx1Header h)
        {
            int numArrays = 0;
            for (int i = 0; i < 13; ++i)
                if (h.offsets[i] != 0) ++numArrays;
            return numArrays;
        }

        public static void dumpVtx1(EndianBinaryReader er, Vtx1 dst)
        {
            int vtx1Offset = (int)er.BaseStream.Length, i;

            //read header
            Vtx1Header h = new Vtx1Header();
            readVtx1Header(er, h);

            int numArrays = countArrays(h);

            //read vertex array format descriptions
            List<ArrayFormat> formats = new List<ArrayFormat>();
            formats.AddRange(new ArrayFormat[numArrays]);
            er.BaseStream.Position = vtx1Offset + h.arrayFormatOffset;
            for (i = 0; i < numArrays; ++i)
                readArrayFormat(er, formats[i]);

            //read arrays
            int j = 0;
            for (i = 0; i < 13; ++i)
            {
                if (h.offsets[i] == 0)
                    continue;

                ArrayFormat currFormat = formats[j];
                int len = getLength(h, i);
                readVertexArray(dst, currFormat, len, er,
                  vtx1Offset + h.offsets[i]);

                ++j;
            }
        }
    }
}
